﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Xna.Framework.Graphics;
using WinFormsGraphicsDevice;
using System.Runtime.InteropServices;
using System.Security;
using SharpMedia.XNA;
using System.Threading;
using SharpMedia;
using Lizk.SimpleHUD;
using SharpMedia.Input;
using System.Diagnostics;

namespace XNAHUDEngine
{
    /// <summary>
    /// 
    /// </summary>
    public partial class XNAForm : Form, Engine
    {


        /// <summary>
        /// Called during render loop after begin draw
        /// </summary>
        public event EventHandler Render;

        public event EventHandler Initialized;

        /// <summary>
        /// Called during update loop before rendering
        /// </summary>
        public event EventHandler DoUpdate;

        long ticksAtLastRender = 0;
        
        public string DebugMessage { get; set; }

        // However many GraphicsDeviceControl instances you have, they all share
        // the same underlying GraphicsDevice, managed by this helper service.
        GraphicsDeviceService graphicsDeviceService;

        /// <summary>
        /// Gets a GraphicsDevice that can be used to draw onto this control.
        /// </summary>
        public GraphicsDevice GraphicsDevice
        {
            get { return graphicsDeviceService.GraphicsDevice; }
        }

        public GraphicsDeviceService GraphicsDeviceService
        {
            get { return graphicsDeviceService; }
        }

        public XNARenderer Renderer { get; set; }

        /// <summary>
        /// Gets an IServiceProvider containing our IGraphicsDeviceService.
        /// This can be used with components such as the ContentManager,
        /// which use this service to look up the GraphicsDevice.
        /// </summary>
        public ServiceContainer Services
        {
            get { return services; }
        }

        ServiceContainer services = new ServiceContainer();

        /// <summary>
        /// Sets Windowed Mode, requires reset to take effect.
        /// </summary>
        public WindowMode Windowed { get; set; }

        /// <summary>
        /// Gets the current window size, uses windowsize if windowed, otherwise it returns the size of the screen, the form is on.
        /// </summary>
        public SizeS CurrentSize
        {
            get
            {
                if (Windowed == WindowMode.Windowed)
                    return new SizeS((int)WindowSize.Width, (int)WindowSize.Height);
                else
                {
                    Size s = Screen.FromHandle(currentHandle).Bounds.Size;
                    return new SizeS(s.Width, s.Height);
                }
            }
        }
        //protected Size screenSize;

        

        MouseHider mouseHider;

        public XNAForm()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque | ControlStyles.UserPaint, true);
            UpdateStyles();
            
            //DoubleBuffered = true;
            //SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
            
            Application.Idle += new EventHandler(UpdateLoop);
            ResizeEnd += new EventHandler(XNAForm_ResizeEnd);
            this.MinimizeBox = true;
            this.MaximizeBox = false;
            //InitializeComponent();
             mouseHider = new MouseHider(this);
             this.HandleCreated += new EventHandler(XNAForm_HandleCreated);
        }
        IntPtr currentHandle;
        void XNAForm_HandleCreated(object sender, EventArgs e)
        {
            currentHandle = Handle;
        }

        void XNAForm_ResizeEnd(object sender, EventArgs e)
        {
            if (Windowed == WindowMode.Windowed)
            {
                WindowSize = new SizeS(ClientSize.Width, ClientSize.Height);
            }
        }
        private bool serviceAdded = false;
        public void InitializeDevice()
        {
            ClientSize = new Size((int)WindowSize.Width, (int)WindowSize.Height) ;

            HandleVista();
            
            graphicsDeviceService = GraphicsDeviceService.AddRef(Handle,
                                                     (int)CurrentSize.Width,
                                                     (int)CurrentSize.Height, Windowed == WindowMode.ExclusiveFullscreen);
            
            // Register the service, so components like ContentManager can find it.
            if(!serviceAdded)
                services.AddService<IGraphicsDeviceService>(graphicsDeviceService);
            serviceAdded = true;

            if (Initialized != null)
                Initialized(this, null);
        }

        private void HandleVista()
        {
            if (Environment.OSVersion.Version.Major >= 6) // if vista
            {
                if (Windowed == WindowMode.ExclusiveFullscreen 
                    && VistaControl.DwmIsCompositionEnabled())
                {
                    VistaControl.DwmEnableComposition(VistaControl.DWM_EC_DISABLECOMPOSITION);
                }
                else if (Windowed != WindowMode.ExclusiveFullscreen 
                    && !VistaControl.DwmIsCompositionEnabled())
                {
                    VistaControl.DwmEnableComposition(VistaControl.DWM_EC_ENABLECOMPOSITION);
                }
            }
        }

        /// <summary>
        /// Updates the loop.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void UpdateLoop(object sender, EventArgs e)
        {
            while (AppStillIdle)
            {
                // Update the global timer
                XNAUtil.UpdateTimer();

                XNAUtil.Wait();
                Renderer.HandleVideoCommandQueue();
                XNAUtil.Resume();

                // if the form is not focused, save som cpu cycles by delaying a little.
                if (!Focused && !Renderer.VideoRunning)
                    Thread.Sleep(20);

                mouseHider.Update(TimeElapsed);

                if (DoUpdate != null)
                    DoUpdate(this, null);




                string error = BeginDraw();
                if (error == null)
                {
                    //GraphicsDevice.Clear(Microsoft.Xna.Framework.Graphics.Color.BlueViolet);

                    // This part ensures we dont render too many frames, caps the cpu usage, who wants a mediacenter that uses 100% cpu when doing nothing?
                    double elapsed = XNAUtil.CurrentTickCount - ticksAtLastRender;
                    double fps = GraphicsDevice.DisplayMode.RefreshRate * 2;
                    double frameTime = (double)TimeSpan.TicksPerSecond / (double)fps;

                    if (elapsed < frameTime)
                    {
                        Thread.Sleep((int)((frameTime - elapsed) / (double)TimeSpan.TicksPerMillisecond));
                    }
                    ticksAtLastRender = XNAUtil.CurrentTickCount;

                    GraphicsDevice.Clear(Microsoft.Xna.Framework.Graphics.Color.Black);

                    if (Render != null)
                        Render(this, null);


                    Microsoft.Xna.Framework.Rectangle sourceRectangle = new Microsoft.Xna.Framework.Rectangle(0, 0, (int)CurrentSize.Width, (int)CurrentSize.Height);

                        try
                        {
                            if (Windowed == WindowMode.Windowed)
                                GraphicsDevice.Present(sourceRectangle, null, this.Handle);
                            else
                                GraphicsDevice.Present(this.Handle);
                        }
                        catch (DeviceLostException)
                        {

                        }
                        
                }
                else
                {
                }
                
            }
        }

        /// <summary>
        /// Attempts to begin drawing the control. Returns an error message string
        /// if this was not possible, which can happen if the graphics device is
        /// lost, or if we are running inside the Form designer.
        /// </summary>
        string BeginDraw()
        {
            // If we have no graphics device, we must be running in the designer.
            if (graphicsDeviceService == null)
            {
                return Text + "\n\n" + GetType();
            }

            // Make sure the graphics device is big enough, and is not lost.
            string deviceResetError = HandleDeviceReset();

            if (!string.IsNullOrEmpty(deviceResetError))
            {
                return deviceResetError;
            }

            // Many GraphicsDeviceControl instances can be sharing the same
            // GraphicsDevice. The device backbuffer will be resized to fit the
            // largest of these controls. But what if we are currently drawing
            // a smaller control? To avoid unwanted stretching, we set the
            // viewport to only use the top left portion of the full backbuffer.
            Viewport viewport = new Viewport();

            viewport.X = 0;
            viewport.Y = 0;

            viewport.Width = (int)CurrentSize.Width;
            viewport.Height = (int)CurrentSize.Height;

            viewport.MinDepth = 0;
            viewport.MaxDepth = 1;

            GraphicsDevice.Viewport = viewport;

            return null;
        }

        /// <summary>
        /// Helper used by BeginDraw. This checks the graphics device status,
        /// making sure it is big enough for drawing the current control, and
        /// that the device is not lost. Returns an error string if the device
        /// could not be reset.
        /// </summary>
        string HandleDeviceReset()
        {
            bool deviceNeedsReset = false;

            switch (GraphicsDevice.GraphicsDeviceStatus)
            {
                case GraphicsDeviceStatus.Lost:
                    // If the graphics device is lost, we cannot use it at all.
                    return "Graphics device lost";

                case GraphicsDeviceStatus.NotReset:
                    // If device is in the not-reset state, we should try to reset it.
                    deviceNeedsReset = true;
                    break;

                default:
                    // If the device state is ok, check whether it is big enough.
                    PresentationParameters pp = GraphicsDevice.PresentationParameters;

                    deviceNeedsReset = (ClientSize.Width > pp.BackBufferWidth) ||
                                       (ClientSize.Height > pp.BackBufferHeight) || pp.IsFullScreen != (Windowed == WindowMode.ExclusiveFullscreen);
                    break;
            }

            // Do we need to reset the device?
            if (deviceNeedsReset)
            {
                HandleVista();
                try
                {
                    graphicsDeviceService.ResetDevice((int)CurrentSize.Width,
                                                      (int)CurrentSize.Height, Windowed == WindowMode.ExclusiveFullscreen);
                    if (Windowed == WindowMode.Windowed)
                        ClientSize = new Size((int)WindowSize.Width, (int)WindowSize.Height);
                }
                catch (Exception e)
                {
                    

                    return "Graphics device reset failed\n\n" + e;
                }
            }

            return null;
        }

        public void ResetDevice()
        {
            HandleDeviceReset();
        }



        #region WndProc
        public delegate void WndProcHandler(ref Message m, ref bool callBase);
        public event WndProcHandler WindowsMessages;
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x112 && (m.WParam.ToInt32() == 0xF170 || m.WParam.ToInt32() == 0xF140))
            {
                // If screensaver wants to start, then dont!
            }
            else
            {
                bool callBase = true;
                if (WindowsMessages != null)
                    WindowsMessages(ref m, ref callBase);
                if (callBase)
                    base.WndProc(ref m);
            }

        }
        #endregion

        #region Application gejl

        private static bool AppStillIdle
        {
            get
            {
                PeekMsg msg;
                return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct PeekMsg
        {
            public IntPtr hWnd;
            public Message msg;
            public IntPtr wParam;
            public IntPtr lParam;
            public uint time;
            public Point p;
        }

        [SuppressUnmanagedCodeSecurity]
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern bool PeekMessage(out PeekMsg msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);

        #endregion


        #region Engine Members
        public SizeS WindowSize { get; set; }


        public new PointS MousePosition
        {
            get
            {
                Point p = this.PointToClient(Cursor.Position);

                return new PointS(p.X, p.Y);
            }
        }

        public void Reset()
        {
            ResetDevice();
        }

        public void Wait()
        {
            XNAUtil.Wait();
        }

        public void Resume()
        {
            XNAUtil.Resume();
        }

        private double lastElapsed;
        private TimeSpan timeElapsed;

        public TimeSpan TimeElapsed
        {
            get 
            { 
                if(lastElapsed != XNAUtil.TimeElapsed) {
                    lastElapsed = XNAUtil.TimeElapsed;
                    timeElapsed = new TimeSpan((long)(XNAUtil.TimeElapsed * Math.Pow(10, 7))); 
                }
                return timeElapsed;
            }
        }

        public bool IsCursorHidden
        {
            get { return mouseHider.CursorHidden; }
        }
        
        public bool StayOnTop
        {
            get
            {
                return this.TopMost;
            }
            set
            {
                Invoke(new MethodInvoker(delegate {
                this.TopMost = value; }));
            }
        }

        #endregion
    }
}
